/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.support;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.springframework.aop.Advisor;
import org.springframework.aop.AopInvocationException;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.IntroductionAwareMethodMatcher;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.TargetClassAware;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodIntrospector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

/**
 * Utility methods for AOP support code.
 *
 * <p>Mainly for internal use within Spring's AOP support.
 *
 * <p>See {@link org.springframework.aop.framework.AopProxyUtils} for a
 * collection of framework-specific AOP utility methods which depend
 * on internals of Spring's AOP framework implementation.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @see org.springframework.aop.framework.AopProxyUtils
 */
public abstract class AopUtils {

	/**
	 * Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
	 * <p>This method additionally checks if the given object is an instance
	 * of {@link SpringProxy}.
	 * @param object the object to check
	 * @see #isJdkDynamicProxy
	 * @see #isCglibProxy
	 */
	public static boolean isAopProxy(@Nullable Object object) {
		return (object instanceof SpringProxy && (Proxy.isProxyClass(object.getClass()) ||
				object.getClass().getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)));
	}

	/**
	 * Check whether the given object is a JDK dynamic proxy.
	 * <p>This method goes beyond the implementation of
	 * {@link Proxy#isProxyClass(Class)} by additionally checking if the
	 * given object is an instance of {@link SpringProxy}.
	 * @param object the object to check
	 * @see java.lang.reflect.Proxy#isProxyClass
	 */
	public static boolean isJdkDynamicProxy(@Nullable Object object) {
		return (object instanceof SpringProxy && Proxy.isProxyClass(object.getClass()));
	}

	/**
	 * Check whether the given object is a CGLIB proxy.
	 * <p>This method goes beyond the implementation of
	 * {@link ClassUtils#isCglibProxy(Object)} by additionally checking if
	 * the given object is an instance of {@link SpringProxy}.
	 * @param object the object to check
	 * @see ClassUtils#isCglibProxy(Object)
	 */
	public static boolean isCglibProxy(@Nullable Object object) {
		return (object instanceof SpringProxy &&
				object.getClass().getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR));
	}

	/**
	 * Determine the target class of the given bean instance which might be an AOP proxy.
	 * <p>Returns the target class for an AOP proxy or the plain class otherwise.
	 * @param candidate the instance to check (might be an AOP proxy)
	 * @return the target class (or the plain class of the given object as fallback;
	 * never {@code null})
	 * @see org.springframework.aop.TargetClassAware#getTargetClass()
	 * @see org.springframework.aop.framework.AopProxyUtils#ultimateTargetClass(Object)
	 */
	public static Class<?> getTargetClass(Object candidate) {
		Assert.notNull(candidate, "Candidate object must not be null");
		Class<?> result = null;
		if (candidate instanceof TargetClassAware) {
			result = ((TargetClassAware) candidate).getTargetClass();
		}
		if (result == null) {
			result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
		}
		return result;
	}

	/**
	 * Select an invocable method on the target type: either the given method itself
	 * if actually exposed on the target type, or otherwise a corresponding method
	 * on one of the target type's interfaces or on the target type itself.
	 * @param method the method to check
	 * @param targetType the target type to search methods on (typically an AOP proxy)
	 * @return a corresponding invocable method on the target type
	 * @throws IllegalStateException if the given method is not invocable on the given
	 * target type (typically due to a proxy mismatch)
	 * @since 4.3
	 * @see MethodIntrospector#selectInvocableMethod(Method, Class)
	 */
	public static Method selectInvocableMethod(Method method, @Nullable Class<?> targetType) {
		if (targetType == null) {
			return method;
		}
		Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
		if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) &&
				SpringProxy.class.isAssignableFrom(targetType)) {
			throw new IllegalStateException(String.format(
					"Need to invoke method '%s' found on proxy for target class '%s' but cannot " +
					"be delegated to target bean. Switch its visibility to package or protected.",
					method.getName(), method.getDeclaringClass().getSimpleName()));
		}
		return methodToUse;
	}

	/**
	 * Determine whether the given method is an "equals" method.
	 * @see java.lang.Object#equals
	 */
	public static boolean isEqualsMethod(@Nullable Method method) {
		return ReflectionUtils.isEqualsMethod(method);
	}

	/**
	 * Determine whether the given method is a "hashCode" method.
	 * @see java.lang.Object#hashCode
	 */
	public static boolean isHashCodeMethod(@Nullable Method method) {
		return ReflectionUtils.isHashCodeMethod(method);
	}

	/**
	 * Determine whether the given method is a "toString" method.
	 * @see java.lang.Object#toString()
	 */
	public static boolean isToStringMethod(@Nullable Method method) {
		return ReflectionUtils.isToStringMethod(method);
	}

	/**
	 * Determine whether the given method is a "finalize" method.
	 * @see java.lang.Object#finalize()
	 */
	public static boolean isFinalizeMethod(@Nullable Method method) {
		return (method != null && method.getName().equals("finalize") &&
				method.getParameterCount() == 0);
	}

	/**
	 * Given a method, which may come from an interface, and a target class used
	 * in the current AOP invocation, find the corresponding target method if there
	 * is one. E.g. the method may be {@code IFoo.bar()} and the target class
	 * may be {@code DefaultFoo}. In this case, the method may be
	 * {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
	 * <p><b>NOTE:</b> In contrast to {@link org.springframework.util.ClassUtils#getMostSpecificMethod},
	 * this method resolves Java 5 bridge methods in order to retrieve attributes
	 * from the <i>original</i> method definition.
	 * @param method the method to be invoked, which may come from an interface
	 * @param targetClass the target class for the current invocation.
	 * May be {@code null} or may not even implement the method.
	 * @return the specific target method, or the original method if the
	 * {@code targetClass} doesn't implement it or is {@code null}
	 * @see org.springframework.util.ClassUtils#getMostSpecificMethod
	 */
	public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
		Class<?> specificTargetClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null);
		Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);
		// If we are dealing with method with generic parameters, find the original method.
		return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
	}

	/**
	 * Can the given pointcut apply at all on the given class?
	 * <p>This is an important test as it can be used to optimize
	 * out a pointcut for a class.
	 * @param pc the static or dynamic pointcut to check
	 * @param targetClass the class to test
	 * @return whether the pointcut can apply on any method
	 */
	public static boolean canApply(Pointcut pc, Class<?> targetClass) {
		return canApply(pc, targetClass, false);
	}

	/**
	 * 判断给定的切入点是否可能在指定类的任何方法上适用。
	 * <p>这是一个重要的测试，因为它可以用于优化，排除对某个类不适用的切入点。
	 * 该方法会检查切入点的类过滤器和方法匹配器，确定是否存在任何方法可能被匹配。
	 *
	 * @param pc 要检查的静态或动态切入点
	 * @param targetClass 要测试的目标类
	 * @param hasIntroductions 该bean的Advisor链是否包含任何Introduction增强
	 * @return 如果切入点可能在目标类的任何方法上适用，则返回true；否则返回false
	 */
	public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
		// 断言切入点不能为空，确保后续操作的安全性
		Assert.notNull(pc, "Pointcut must not be null");

		// 首先检查切入点的类过滤器是否匹配目标类
		// 类过滤器是切入点的第一级检查，如果不匹配则直接返回false
		if (!pc.getClassFilter().matches(targetClass)) {
			return false;
		}

		// 获取切入点的方法匹配器，用于后续方法级别的匹配检查
		MethodMatcher methodMatcher = pc.getMethodMatcher();

		// 如果方法匹配器是MethodMatcher.TRUE，表示匹配所有方法
		// 这种情况下无需进一步检查，直接返回true
		if (methodMatcher == MethodMatcher.TRUE) {
			// 无需遍历方法，因为无论如何都匹配所有方法
			return true;
		}

		// 声明Introduction感知的方法匹配器变量
		// 该类型的匹配器能够处理Introduction增强相关的匹配逻辑
		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;

		// 如果方法匹配器实现了IntroductionAwareMethodMatcher接口
		// 则进行类型转换，以便后续调用其特殊的匹配方法
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}

		// 创建一个有序集合来存储要检查的类
		// 包括目标类本身及其实现的所有接口，确保全面检查
		Set<Class<?>> classes = new LinkedHashSet<>();

		// 如果目标类不是代理类，则将其用户类（实际被代理的类）添加到集合中
		// 对于CGLIB代理，获取的是被代理类的子类；对于JDK动态代理，获取的是接口
		if (!Proxy.isProxyClass(targetClass)) {
			classes.add(ClassUtils.getUserClass(targetClass));
		}

		// 将目标类实现的所有接口添加到集合中
		// 这确保我们检查目标类及其接口中定义的所有方法
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

		// 遍历所有要检查的类（目标类及其接口）
		for (Class<?> clazz : classes) {
			// 获取当前类声明的所有方法（包括私有、受保护和公共方法）
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);

			// 遍历当前类的每个方法，检查是否有方法匹配切入点
			for (Method method : methods) {
				// 根据方法匹配器类型选择不同的匹配方式
				// 如果是Introduction感知的方法匹配器，则调用其特殊的matches方法
				// 该方法会额外考虑Introduction增强的影响
				// 否则调用普通的matches方法进行匹配检查
				if (introductionAwareMethodMatcher != null ?
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
						methodMatcher.matches(method, targetClass)) {
					// 只要找到一个匹配的方法，就立即返回true,因为只要有一个方法增强了，这个类就得被代理
					// 表示切入点可以应用于目标类的至少一个方法
					return true;
				}
			}
		}

		// 如果遍历所有类和方法后都没有找到匹配的方法
		// 则返回false，表示切入点不能应用于目标类的任何方法
		return false;
	}
	/**
	 * Can the given advisor apply at all on the given class?
	 * This is an important test as it can be used to optimize
	 * out a advisor for a class.
	 * @param advisor the advisor to check
	 * @param targetClass class we're testing
	 * @return whether the pointcut can apply on any method
	 */
	public static boolean canApply(Advisor advisor, Class<?> targetClass) {
		return canApply(advisor, targetClass, false);
	}

	/**
	 * 判断给定的Advisor是否可能在指定类的任何方法上适用。
	 * <p>这是一个重要的测试，因为它可以用于优化，排除对某个类不适用的advisor。
	 * 此版本还考虑了Introduction增强（针对IntroductionAwareMethodMatcher）。
	 * @param advisor 要检查的advisor
	 * @param targetClass 要测试的目标类
	 * @param hasIntroductions 该bean的advisor链是否包含任何Introduction增强
	 * @return 如果advisor可能在目标类的任何方法上适用，则返回true；否则返回false
	 */
	public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
		// 处理IntroductionAdvisor类型的advisor
		// IntroductionAdvisor专门用于引入新接口和实现，只需检查其类过滤器
		if (advisor instanceof IntroductionAdvisor) {
			// 对于IntroductionAdvisor，直接调用其类过滤器的matches方法
			// 如果类过滤器匹配目标类，则认为该advisor适用
			return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
		}
		// 处理PointcutAdvisor类型的advisor
		// PointcutAdvisor包含切入点，需要检查切入点是否匹配
		else if (advisor instanceof PointcutAdvisor) {
			// 将advisor转换为PointcutAdvisor
			PointcutAdvisor pca = (PointcutAdvisor) advisor;
			// 调用重载的canApply方法，检查切入点是否适用于目标类
			// 该方法会考虑类过滤器和方法匹配器
			return canApply(pca.getPointcut(), targetClass, hasIntroductions);
		}
		// 处理其他类型的advisor（如没有切入点的advisor）
		else {
			// 如果advisor不包含切入点（如BeforeAdvice、AfterAdvice等）
			// 则认为它适用于所有类，返回true
			// 这种情况下，advisor会应用于所有方法，通常用于全局增强
			return true;
		}
	}

	/**
	 * 从候选增强器列表中筛选出所有能够应用于指定目标类的增强器。
	 * 该方法会评估每个增强器的切入点(Pointcut)是否匹配目标类及其方法，
	 * 是 Spring AOP 确定增强器适用性的核心算法实现。
	 * 	 *
	 * <p>筛选逻辑区分两种增强器类型：
	 * <ol>
	 *   <li>IntroductionAdvisor：用于引入新接口的增强器，需实现特定接口匹配逻辑</li>
	 *   <li>普通 Advisor：基于方法匹配的增强器，需检查切入点表达式</li>
	 * </ol>
	 *
	 * <p>执行流程：
	 * <ol>
	 *   <li>首先处理所有 IntroductionAdvisor 类型的增强器：
	 *     <ul>
	 *       <li>调用 canApply(Advisor, Class) 检查是否能应用于目标类</li>
	 *       <li>此类增强器必须通过 ClassFilter 匹配才能被添加</li>
	 *     </ul>
	 *   </li>
	 *   <li>记录是否存在 IntroductionAdvisor，用于后续判断</li>
	 *   <li>处理所有普通 Advisor 类型的增强器：
	 *     <ul>
	 *       <li>跳过已处理的 IntroductionAdvisor</li>
	 *       <li>调用 canApply(Advisor, Class, boolean) 进行匹配判断</li>
	 *       <li>考虑 IntroductionAdvisor 可能带来的接口引入对匹配结果的影响</li>
	 *     </ul>
	 *   </li>
	 * </ol>
	 *
	 * <p>匹配判断逻辑：
	 * <ul>
	 *   <li>对于 IntroductionAdvisor，仅检查 ClassFilter 是否匹配</li>
	 *   <li>对于普通 Advisor，检查 ClassFilter 并分析目标类的所有方法，
	 *       只要有一个方法匹配则认为增强器适用</li>
	 *   <li>处理目标类是接口的特殊情况</li>
	 *   <li>考虑引入增强器可能带来的接口实现变化</li>
	 * </ul>
	 *
	 * <p>性能优化：
	 * <ul>
	 *   <li>对于单例增强器，匹配结果会被缓存以提高效率</li>
	 *   <li>对接口的匹配会预先检查，避免不必要的方法遍历</li>
	 *   <li>尽早排除明显不匹配的增强器</li>
	 * </ul>
	 *
	 * @param candidateAdvisors 待评估的候选增强器列表
	 * @param clazz             目标类
	 * @return 能够应用于该目标类的增强器子列表（可能与原列表相同）
	 *
	 * @see #canApply(Advisor, Class) - 判断 IntroductionAdvisor 适用性的方法
	 * @see #canApply(Advisor, Class, boolean) - 判断普通 Advisor 适用性的方法
	 * @see org.springframework.aop.ClassFilter - 类过滤器接口
	 * @see org.springframework.aop.MethodMatcher - 方法匹配器接口
	 * @see org.springframework.aop.introduction.IntroductionAdvisor - 引入增强器接口
	 */
	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		// 快速返回空列表以优化性能
		if (candidateAdvisors.isEmpty()) {
			return candidateAdvisors;
		}

		List<Advisor> eligibleAdvisors = new ArrayList<>();

		// 第一阶段：处理所有 IntroductionAdvisor 类型的增强器
		for (Advisor candidate : candidateAdvisors) {
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}

		// 记录是否存在 IntroductionAdvisor，影响后续普通增强器的匹配逻辑
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();

		// 第二阶段：处理所有普通 Advisor 类型的增强器
		for (Advisor candidate : candidateAdvisors) {
			// 跳过已经在第一阶段处理过的 IntroductionAdvisor
			if (candidate instanceof IntroductionAdvisor) {
				continue;
			}

			// 检查普通增强器是否能应用于目标类，考虑可能存在的接口引入,找被匹配上的Advisor对象
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}

		return eligibleAdvisors;
	}

	/**
	 * Invoke the given target via reflection, as part of an AOP method invocation.
	 * @param target the target object
	 * @param method the method to invoke
	 * @param args the arguments for the method
	 * @return the invocation result, if any
	 * @throws Throwable if thrown by the target method
	 * @throws org.springframework.aop.AopInvocationException in case of a reflection error
	 */
	@Nullable
	public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
			throws Throwable {

		// Use reflection to invoke the method.
		try {
			ReflectionUtils.makeAccessible(method);
			return method.invoke(target, args);
		}
		catch (InvocationTargetException ex) {
			// Invoked method threw a checked exception.
			// We must rethrow it. The client won't see the interceptor.
			throw ex.getTargetException();
		}
		catch (IllegalArgumentException ex) {
			throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
					method + "] on target [" + target + "]", ex);
		}
		catch (IllegalAccessException ex) {
			throw new AopInvocationException("Could not access method [" + method + "]", ex);
		}
	}

}
